Skip to content

Fix invisible RTL text on Qt backend (MeasureWidths uses visual instead of logical positions) - #1116

Closed
NoamRamadi wants to merge 1 commit into
dail8859:masterfrom
NoamRamadi:fix/qt-rtl-measure-widths
Closed

Fix invisible RTL text on Qt backend (MeasureWidths uses visual instead of logical positions)#1116
NoamRamadi wants to merge 1 commit into
dail8859:masterfrom
NoamRamadi:fix/qt-rtl-measure-widths

Conversation

@NoamRamadi

Copy link
Copy Markdown

Summary

RTL-only lines (e.g. typing Hebrew alone on a line) render invisibly. Mixed LTR+RTL lines (e.g. Hi היי) render correctly, which pointed at a positioning bug rather than a font/shaping issue.

Root cause: Surface::MeasureWidths() is expected to return per-character pixel positions that are monotonically non-decreasing in logical (document) order — Scintilla's EditView.cxx uses this to place styled runs, the caret, and selection highlights along a line. The Win32 (SurfaceD2D::MeasurePositions) and Cocoa backends already provide this by summing cluster advance widths in text order, independent of visual/bidi reordering.

The Qt backend's SurfaceImpl::MeasureWidths/MeasureWidthsUTF8 (PlatQt.cpp) instead used QTextLine::cursorToX(), which returns visual bidi-reordered positions. For a line containing only RTL characters, Qt auto-detects the paragraph as right-to-left, and cursorToX() returns positions that decrease as the logical index increases — violating the monotonicity invariant EditView.cxx depends on. This pushes glyphs for the line outside the visible/clipped drawing rectangle, making pure-RTL lines appear invisible. A leading LTR run (as in Hi היי) happens to anchor enough of the line in-bounds to look correct.

Fix

Changed MeasureWidths (UTF-8 codepage branch) and MeasureWidthsUTF8 in thirdparty/scintilla/qt/ScintillaEditBase/PlatQt.cpp to accumulate each character's advance width independently via QFontMetricsF::horizontalAdvance(), summed in logical order — guaranteeing the required monotonic-non-decreasing invariant regardless of text direction, matching the approach already used on Win32/Cocoa.

Glyph painting (DrawTextNoClip/DrawTextClipped, etc.) already goes through QPainter::drawText(), which performs correct Unicode bidi shaping/reordering on its own — so visual rendering quality for LTR text and RTL glyph shaping is unaffected by this change.

Known trade-off: per-character measurement loses cross-character contextual shaping (ligatures, ligature-based kerning, Arabic's position-dependent glyph forms) for measurement purposes only. Hebrew (the reported case) has minimal contextual shaping, so this is not noticeable. Arabic text should no longer be invisible, though pixel-perfect character spacing for heavily-shaped scripts could still have minor rounding differences from what QPainter::drawText actually paints — a possible follow-up if anyone hits it.

Out of scope for this PR: implementing SurfaceImpl::Layout()/IScreenLineLayout and wiring up Message::SetBidirectional (currently unimplemented on the Qt backend, unlike Win32/Cocoa). That's needed for fully correct RTL caret movement and multi-rectangle RTL selection highlighting, but is a separate, larger feature addition — not required to fix the reported invisible-text bug, since EditModel::BidirectionalEnabled() is never true today on Qt builds (nothing calls SetBidirectional).

Test plan

  • Built locally via cmake -S . -B build -DCMAKE_PREFIX_PATH=$(brew --prefix qt) -DCMAKE_BUILD_TYPE=Debug && cmake --build build --target NotepadNext -j
  • Typed היי alone on a line — now visible (previously invisible)
  • Typed Hi היי on a line — still renders correctly (no regression)
  • Would appreciate additional testing with Arabic and other RTL/complex scripts from anyone able to test them

🤖 Generated with Claude Code

Scintilla's rendering code (EditView.cxx) requires Surface::MeasureWidths()
to return per-character pixel positions that are monotonically non-decreasing
in logical (document) order, so it can place styled runs, the caret, and
selection highlights along a line. The Win32 and Cocoa backends already honor
this by summing cluster advance widths in text order.

The Qt backend instead used QTextLine::cursorToX(), which returns visual
(bidi-reordered) positions. For a line containing only RTL text (e.g. Hebrew),
Qt auto-detects the paragraph as right-to-left and cursorToX() returns
positions that decrease as the logical index increases, breaking the
monotonicity invariant. This pushes the line's glyphs outside the visible/
clipped drawing rectangle, making RTL-only lines appear invisible, while
mixed LTR+RTL lines (e.g. "Hi היי") happened to stay partially in bounds.

Fix MeasureWidths/MeasureWidthsUTF8 in PlatQt.cpp to accumulate each
character's advance width independently via QFontMetricsF::horizontalAdvance,
summed in logical order, guaranteeing the required monotonic invariant
regardless of text direction. Glyph painting already goes through
QPainter::drawText, which performs correct bidi shaping/reordering on its
own, so visual rendering quality is unaffected.
@NoamRamadi

Copy link
Copy Markdown
Author

Note on the failing checks: both the Codespell failure (a pre-existing `alot` typo in `src/dialogs/MainWindow.cpp`, a file this PR doesn't touch) and the `macos-latest` build failures for Qt 6.5/6.8 (`ld: framework 'AGL' not found`) already reproduce identically on `master` at c627dea (see the latest "Build Notepad Next" / "Codespell" runs on master), so they're unrelated to this change. All Linux and Windows builds pass, and macOS with Qt 6.10 passes.

@dail8859

Copy link
Copy Markdown
Owner

The Scintilla code here is just a direct copy of the upstream https://scintilla.org/ project. Any bug fixes would need submitted to the upstream project to review/accept.

@dail8859 dail8859 closed this Aug 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants